<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Expire" content="0"/>
  <title>click_link_test.html</title>
  <script src="test_bootstrap.js"></script>
  <script type="text/javascript">
    goog.require('bot.action');
    goog.require('bot.locators');
    goog.require('bot.userAgent');
    goog.require('goog.Uri');
    goog.require('goog.debug.DivConsole');
    goog.require('goog.debug.Logger');
    goog.require('goog.dom');
    goog.require('goog.events');
    goog.require('goog.events.EventType');
    goog.require('goog.math.Coordinate');
    goog.require('goog.style');
    goog.require('goog.testing.AsyncTestCase');
    goog.require('goog.testing.jsunit');
    goog.require('goog.userAgent');
    goog.require('goog.userAgent.product');
  </script>
  <script type="text/javascript">
    var GECKO6 = goog.userAgent.GECKO && bot.userAgent.isEngineVersion(6);
    var divConsole;
    var iframe, iframeWindow, other, otherWindow;
    var link, link2;
    var findElement = bot.locators.findElement;
    var findElements = bot.locators.findElements;
    var log = goog.debug.Logger.getLogger('click_link_test');
    var asyncTestCase = null;

    function setUp() {
      iframe = bot.locators.findElement({id: 'iframe'});
      iframeWindow = goog.dom.getFrameContentWindow(iframe);
      otherFrame = bot.locators.findElement({id: 'other'});
      otherFrameWindow = goog.dom.getFrameContentWindow(otherFrame);
      continueAfterLoad(iframe, goog.nullFunction);
      iframeWindow.location = resolveUrl('testdata/click_iframe.html');

      link = bot.locators.findElement({id: 'link'});
      link2 = bot.locators.findElement({id: 'link2'});
      goog.events.removeAll(link);
      goog.events.removeAll(link2);
      link.blur();
      link2.blur();
      window.focus();
    }

    function checkActionCompatibility(action) {
      if (action == bot.action.tap) {
        return bot.events.SUPPORTS_TOUCH_EVENTS;
      }
      return true;
    }

    function continueAfterLoad(iframe, fn) {
      asyncTestCase.waitForAsync('Waiting for ' + iframe.id + ' to load');
      return goog.events.listenOnce(iframe, 'load', function() {
        asyncTestCase.continueTesting();
        fn();
      });
    }

    function continueAfterTimeout(fn) {
      asyncTestCase.waitForAsync('Waiting a cycle for page to update.');
      window.setTimeout(function() {
        asyncTestCase.continueTesting();
        fn();
      // Firefox 6 currently needs a fairly long timeout before a hashchange is
      // completed, while other browsers complete it on the next event cycle.
      // TODO(user): Remove superfluous link following from device.js and
      // then check whether this longer timeout is still necessary.
      }, GECKO6 ? 250 : 0);
    }

    function resolveUrl(url) {
      return goog.Uri.resolve(window.location.href, url).toString();
    }

    function getClickTarget(url) {
      var doc = goog.dom.getFrameContentDocument(iframe);
      var target = bot.locators.findElement({'id': 'iframeClickTarget'}, doc);
      target.href = resolveUrl(url);
      return target;
    }

    function executesDefaultHandler(action) {
      if (!checkActionCompatibility(action)) {
        return;
      }
      var clickTarget = findElement({id: 'clickTarget'});
      // Make sure no other event listeners are interfering.
      goog.events.removeAll(clickTarget);

      // Use a random value as we don't want to keep an old
      // misleading hash in the url.
      var targetHref = '#' + Math.random();
      clickTarget.href = targetHref;

      continueAfterTimeout(function() {
        assertEquals(targetHref, window.location.hash);
      });
      action(clickTarget);
    }

    var testClickExecutesDefaultHandler =
        goog.partial(executesDefaultHandler, bot.action.click);

    var testTapExecutesDefaultHandler =
        goog.partial(executesDefaultHandler, bot.action.tap);

    function nestedElementExecutesDefaultHandler(action) {
      if (!checkActionCompatibility(action)) {
        return;
      }
      var parent = findElement({id: 'clickTargetWithAChild'});

      // Use a random value as we don't want to keep an old
      // misleading hash in the url.
      var targetHref = '#' + Math.random();
      parent.href = targetHref;

      continueAfterTimeout(function() {
        assertEquals(targetHref, window.location.hash);
      });
      var clickTarget = goog.dom.getFirstElementChild(parent);
      action(clickTarget);
    }

    var testClickNestedElementExecutesDefaultHandler =
        goog.partial(nestedElementExecutesDefaultHandler, bot.action.click);

    var testTapNestedElementExecutesDefaultHandler =
        goog.partial(nestedElementExecutesDefaultHandler, bot.action.tap);

    function correctlyResolvesEmptyFragment(action) {
      if (!checkActionCompatibility(action)) {
        return;
      }
      // A bug in the closure goog.Uri module once prevented this from working.
      var clickTarget = findElement({id: 'clickTarget'});
      // Make sure no other event listeners are interfering.
      goog.events.removeAll(clickTarget);

      continueAfterTimeout(function() {
        var windowHref = window.location.href;
        assertEquals('#', windowHref.charAt(windowHref.length - 1));
      });
      clickTarget.href = '#';
      action(clickTarget);
    }

    var testClickCorrectlyResolvesEmptyFragment =
        goog.partial(correctlyResolvesEmptyFragment, bot.action.click);

    var testTapCorrectlyResolvesEmptyFragment =
        goog.partial(correctlyResolvesEmptyFragment, bot.action.tap);

    function absoluteUrlInAnIframeExecutesDefaultHandler(action) {
      if (!checkActionCompatibility(action)) {
        return;
      }
      var domHelper = goog.dom.getDomHelper(iframeWindow);
      var clickTarget = domHelper.getElement('iframeClickTarget');

      // Use a random value as we don't want to keep an old
      // misleading hash in the url.
      var targetHref = goog.Uri.resolve(iframeWindow.location,
          '#' + Math.random()).toString();

      // Let's make sure it is an absolute url.
      assertContains('http', targetHref);

      clickTarget.href = targetHref;
      action(clickTarget);
      continueAfterTimeout(function() {
        assertEquals(targetHref, iframeWindow.location.href);
      });
    }

    var testClickAbsoluteUrlInAnIframeExecutesDefaultHandler =
        goog.partial(absoluteUrlInAnIframeExecutesDefaultHandler,
                     bot.action.click);

    var testTapAbsoluteUrlInAnIframeExecutesDefaultHandler =
        goog.partial(absoluteUrlInAnIframeExecutesDefaultHandler,
                     bot.action.tap);

    function absoluteServerPathAnchorInAnIframeExecutesDefaultHandler(action) {
      if (!checkActionCompatibility(action)) {
        return;
      }
      var domHelper = goog.dom.getDomHelper(iframeWindow);
      var clickTarget = domHelper.getElement('iframeClickTarget');

      // Use a random value as we don't want to keep an old
      // misleading hash in the url.
      var testPath = window.location.pathname;
      var targetHref = testPath.substring(0, testPath.lastIndexOf('/')) +
          '/testdata/click_iframe.html#' + Math.random();
      clickTarget.href = targetHref;
      action(clickTarget);
      continueAfterTimeout(function() {
        assertContains(targetHref, iframeWindow.location.href);
      });
    }

    var testClickAbsoluteServerPathAnchorInAnIframeExecutesDefaultHandler =
        goog.partial(absoluteServerPathAnchorInAnIframeExecutesDefaultHandler,
                     bot.action.click);

    var testTapAbsoluteServerPathAnchorInAnIframeExecutesDefaultHandler =
        goog.partial(absoluteServerPathAnchorInAnIframeExecutesDefaultHandler,
                     bot.action.tap);

    function relativeServerPathAnchorInAnIframeExecutesDefaultHandler(action) {
      if (!checkActionCompatibility(action)) {
        return;
      }
      var domHelper = goog.dom.getDomHelper(iframeWindow);
      var clickTarget = domHelper.getElement('iframeClickTarget');

      // Use a random value as we don't want to keep an old
      // misleading hash in the url.
      var targetHref = 'click_iframe.html#' + Math.random();
      clickTarget.href = targetHref;
      action(clickTarget);
      continueAfterTimeout(function() {
        assertContains(targetHref, iframeWindow.location.href);
      });
    }

    var testClickRelativeServerPathAnchorInAnIframeExecutesDefaultHandler =
        goog.partial(relativeServerPathAnchorInAnIframeExecutesDefaultHandler,
                     bot.action.click);

    var testTapRelativeServerPathAnchorInAnIframeExecutesDefaultHandler =
        goog.partial(relativeServerPathAnchorInAnIframeExecutesDefaultHandler,
                     bot.action.tap);

    function hashOnlyAnchorInAnIframeExecutesDefaultHandler(action) {
      if (!checkActionCompatibility(action)) {
        return;
      }
      var domHelper = goog.dom.getDomHelper(iframeWindow);
      var clickTarget = domHelper.getElement('iframeClickTarget');

      // Use a random value as we don't want to keep an old
      // misleading hash in the url.
      var targetHref = '#' + Math.random();
      clickTarget.href = targetHref;
      action(clickTarget);
      continueAfterTimeout(function() {
        assertEquals(targetHref, iframeWindow.location.hash);
      });
    }

    var testClickHashOnlyAnchorInAnIframeExecutesDefaultHandler =
        goog.partial(hashOnlyAnchorInAnIframeExecutesDefaultHandler,
                     bot.action.click);

    var testTapHashOnlyAnchorInAnIframeExecutesDefaultHandler =
        goog.partial(hashOnlyAnchorInAnIframeExecutesDefaultHandler,
                     bot.action.tap);

    function nestedElementInAnIframeExecutesDefaultHandler(action) {
      if (!checkActionCompatibility(action)) {
        return;
      }
      var domHelper = goog.dom.getDomHelper(iframeWindow);
      var parent = domHelper.getElement('iframeClickTargetWithAChild');
      var clickTarget = goog.dom.getFirstElementChild(parent);

      // Use a random value as we don't want to keep an old
      // misleading hash in the url.
      var targetHref = '#' + Math.random();
      parent.href = targetHref;
      action(clickTarget);
      continueAfterTimeout(function() {
        assertEquals(targetHref, iframeWindow.location.hash);
      });
    }

    var testClickNestedElementInAnIframeExecutesDefaultHandler =
        goog.partial(nestedElementInAnIframeExecutesDefaultHandler,
                     bot.action.click);

    var testTapHashNestedElementInAnIframeExecutesDefaultHandler =
        goog.partial(nestedElementInAnIframeExecutesDefaultHandler,
                     bot.action.tap);

    function doesNotFollowLinkWhenEventDefaultPrevented(action) {
      if (!checkActionCompatibility(action)) {
        return;
      }
      var clickTarget = findElement({id: 'clickTarget'});
      // Make sure no other event listeners are interfering.
      goog.events.removeAll(clickTarget);

      var previousLocation = window.location.href;
      clickTarget.href = '#' + Math.random();
      goog.events.listen(clickTarget, goog.events.EventType.CLICK, function(e) {
        e.preventDefault();
      });

      action(clickTarget);
      continueAfterTimeout(function() {
        assertEquals(previousLocation, window.location.href);
      });
    }

    var testClickDoesNotFollowLinkWhenEventDefaultPrevented =
        goog.partial(doesNotFollowLinkWhenEventDefaultPrevented,
                     bot.action.click);

    var testTapDoesNotFollowLinkWhenEventDefaultPrevented =
        goog.partial(doesNotFollowLinkWhenEventDefaultPrevented,
                     bot.action.tap);

    function doNotFollowLinkWhenEventDefaultPreventedWithInlineHandler(action) {
      if (!checkActionCompatibility(action)) {
        return;
      }
      var clickTarget = findElement({id: 'clickTargetWithInlineHandler'});

      var previousLocation = window.location.href;
      clickTarget.href = '#' + Math.random();

      action(clickTarget);
      continueAfterTimeout(function() {
        assertEquals(previousLocation, window.location.href);
      });
    }

    var testClickDoNotFollowLinkWhenEventDefaultPreventedWithInlineHandler =
        goog.partial(doNotFollowLinkWhenEventDefaultPreventedWithInlineHandler,
                     bot.action.click);

    var testTapDoNotFollowLinkWhenEventDefaultPreventedWithInlineHandler =
        goog.partial(doNotFollowLinkWhenEventDefaultPreventedWithInlineHandler,
                     bot.action.tap);

    function doesNotFollowLinkWhenNoHrefIsGiven(action) {
      if (!checkActionCompatibility(action)) {
        return;
      }
      var clickTarget = findElement({id: 'clickTargetWithNoHref'});
      // Make sure no other event listeners are interfering.
      goog.events.removeAll(clickTarget);
      var previousLocation = window.location.href;

      action(clickTarget);
      continueAfterTimeout(function() {
        assertEquals(previousLocation, window.location.href);
      });
    }

    var testClickDoesNotFollowLinkWhenNoHrefIsGiven = goog.partial(
        doesNotFollowLinkWhenNoHrefIsGiven, bot.action.click);

    var testTapDoesNotFollowLinkWhenNoHrefIsGiven = goog.partial(
        doesNotFollowLinkWhenNoHrefIsGiven, bot.action.tap);

    function nestedElelementDoesNotFollowLinkWhenNoHrefIsGiven(action) {
      if (!checkActionCompatibility(action)) {
        return;
      }
      var clickTarget = findElement({id: 'clickTargetWithAChildAndNoHref'});

      var previousLocation = window.location.href;
      action(clickTarget);
      assertContains(previousLocation, window.location.href);
    }

    var testClickNestedElelementDoesNotFollowLinkWhenNoHrefIsGiven =
        goog.partial(nestedElelementDoesNotFollowLinkWhenNoHrefIsGiven,
                     bot.action.click);

    var testTapNestedElelementDoesNotFollowLinkWhenNoHrefIsGiven =
        goog.partial(nestedElelementDoesNotFollowLinkWhenNoHrefIsGiven,
                     bot.action.tap);

    function doesNotEncodeReservedButAllowedCharactersInQuery(action) {
      if (!checkActionCompatibility(action)) {
        return;
      }
      // A bug in the closure goog.Uri module once prevented this from working.
      var domHelper = goog.dom.getDomHelper(iframeWindow);
      var clickTarget = domHelper.getElement('iframeClickTarget');

      var targetHref = '?a=?/+';
      clickTarget.href = targetHref;
      continueAfterLoad(iframe, function() {
        assertEquals(targetHref, iframeWindow.location.search);
      });
      action(clickTarget);
    }

    var testClickDoesNoteEncodeReservedButAllowedCharactersInQuery =
        goog.partial(doesNotEncodeReservedButAllowedCharactersInQuery,
                     bot.action.click);

    var testTapDoesNoteEncodeReservedButAllowedCharactersInQuery =
        goog.partial(doesNotEncodeReservedButAllowedCharactersInQuery,
                     bot.action.tap);

    function absoluteAnchorInAnIframeExecutesDefaultHandler(action) {
      if (!checkActionCompatibility(action)) {
        return;
      }
      var clickTarget = getClickTarget('testdata/click_destination.html');
      var targetHref = clickTarget.href;
      continueAfterLoad(iframe, function() {
        assertEquals(targetHref, iframeWindow.location.href);
      });
      action(clickTarget);
    }

    var testClickAbsoluteAnchorInAnIframeExecutesDefaultHandler =
        goog.partial(absoluteAnchorInAnIframeExecutesDefaultHandler,
                     bot.action.click);

    var testTapAbsoluteAnchorInAnIframeExecutesDefaultHandler =
        goog.partial(absoluteAnchorInAnIframeExecutesDefaultHandler,
                     bot.action.tap);

    function linkThatCausesContentToLoadInAnotherFrame(action) {
      if (!checkActionCompatibility(action)) {
        return;
      }
      var clickTarget = getClickTarget('testdata/click_destination.html');
      var targetHref = clickTarget.href;
      clickTarget.target = 'other';
      continueAfterLoad(otherFrame, function() {
        assertEquals(targetHref, otherFrameWindow.location.href);
      });
      action(clickTarget);
    }

    var testClickLinkThatCausesContentToLoadInAnotherFrame =
        goog.partial(linkThatCausesContentToLoadInAnotherFrame,
                     bot.action.click);

    var testTapLinkThatCausesContentToLoadInAnotherFrame =
        goog.partial(linkThatCausesContentToLoadInAnotherFrame,
                     bot.action.tap);

    function onSelfPageReloadsPage(action) {
      if (!checkActionCompatibility(action)) {
        return;
      }
      var clickTarget = getClickTarget(iframeWindow.location.href);
      var targetHref = clickTarget.href;
      continueAfterLoad(iframe, function() {
        assertEquals(targetHref, iframeWindow.location.href);
      });
      action(clickTarget);
    }

    var testClickOnSelfPageReloadsPage = goog.partial(onSelfPageReloadsPage,
                                                      bot.action.click);

    var testTapOnSelfPageReloadsPage = goog.partial(onSelfPageReloadsPage,
                                                    bot.action.tap);

    function actionOnHashDoesNotReloadThePage(action) {
      if (!checkActionCompatibility(action)) {
        return;
      }
      var clickTarget = getClickTarget('');
      clickTarget.href = '#';
      // Fail immediately if the iframe is loaded.
      var id = continueAfterLoad(iframe, fail);
      action(clickTarget);

      // After a delay, assume page is not going to load and pass the test.
      window.setTimeout(function() {
        asyncTestCase.continueTesting();
        goog.events.unlistenByKey(id);
      }, 250);
    }

    var testClickOnHashDoesNotReloadThePage = goog.partial(
        actionOnHashDoesNotReloadThePage, bot.action.click);

    var testTapOnHashDoesNotReloadThePage = goog.partial(
        onSelfPageReloadsPage, bot.action.tap);

    function focusOnAnElementBeforeFinishingClickSequence(action) {
      if (!checkActionCompatibility(action)) {
        return;
      }
      goog.events.listen(link, goog.events.EventType.FOCUS, function() {
        asyncTestCase.continueTesting();
      });

      asyncTestCase.waitForAsync('Waiting for link focus');
      action(link);
    }

    var testClickFocusOnAnElementBeforeFinishingClickSequence = goog.partial(
        focusOnAnElementBeforeFinishingClickSequence, bot.action.click);

    var testTapFocusOnAnElementBeforeFinishingClickSequence = goog.partial(
        focusOnAnElementBeforeFinishingClickSequence, bot.action.tap);

    function focusOnAnElementWhenMousedownChangesFocus(action) {
      if (!checkActionCompatibility(action)) {
        return;
      }
      var mousedownPreemptsFocus = action == bot.action.tap ? false :
                                   goog.userAgent.GECKO || goog.userAgent.IE;
      goog.events.listen(link, goog.events.EventType.MOUSEDOWN, function() {
        link2.focus();
      });
      goog.events.listen(link, goog.events.EventType.FOCUS, function() {
        asyncTestCase.continueTesting();
        assert(!mousedownPreemptsFocus);
      });
      goog.events.listen(link2, goog.events.EventType.FOCUS, function() {
        // If the mousedown will preempt the focus, delay the continue testing
        // call for a short delay to make sure the above assert never triggers.
        if (mousedownPreemptsFocus) {
          window.setTimeout(function() {
            asyncTestCase.continueTesting();
          }, 100);
        }
      });

      asyncTestCase.waitForAsync('Waiting for link focus');
      action(link);
    }

    var testClickFocusOnAnElementWhenMousedownChangesFocus = goog.partial(
        focusOnAnElementWhenMousedownChangesFocus, bot.action.click);

    var testTapFocusOnAnElementWhenMousedownChangesFocus = goog.partial(
        focusOnAnElementWhenMousedownChangesFocus, bot.action.tap);
  </script>
</head>
<body>
  <form action="javascript:void(0)">
    <div id="log"></div>
    <script>
      divConsole = new goog.debug.DivConsole(goog.dom.$('log'));
      divConsole.setCapturing(true);
    </script>
  </form>
  <a id="clickTarget">Click me!</a>
  <a id="clickTargetWithNoHref">Click me!</a>
  <a id="clickTargetWithAChild">Click <strong>the nested</strong> element</a>
  <a id="clickTargetWithAChildAndNoHref">Click <strong>the nested</strong> element with no href</a>
  <a id="clickTargetWithInlineHandler" onclick="return false;">Click me, I will not follow the link!</a>
  <div>
  <div style="width:200px; height:100px;
                border:1px solid black; padding:10px">Click Me</div>
  </div>
  <script>
    asyncTestCase = goog.testing.AsyncTestCase.createAndInstall();
    // Set longer timeout due to android emulator slowness.
    if (goog.userAgent.product.ANDROID) {
      asyncTestCase.stepTimeout = 5000;
    }
  </script>
  <iframe id="iframe"></iframe>
  <iframe id="other" name="other"></iframe>
  <a id="link" href="javascript:void(0)">link</a>
  <a id="link2" href="javascript:void(0)">link2</a>
</body>
</html>
